package org.dragonnova.meetingclient.core.net.socket;

import org.dragonnova.meetingclient.core.message.GroupMessage;
import org.dragonnova.meetingclient.core.message.Message;
import org.dragonnova.meetingclient.core.message.MessageBuilder;

import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by hucn on 2016/6/3.
 * Description:
 */
public abstract class NetWorker {

    protected boolean waitFlag = true;

    private final static int VOICE_MAX_QUEUE_SIZE = 200;
    protected UdpSocket mUdpSocket;
    private ExecutorService mNetPool = Executors.newFixedThreadPool(3);
    private ExecutorService mHandlerPool = Executors.newCachedThreadPool();
    private LinkedBlockingQueue<UdpPacket> mSendQueue = new LinkedBlockingQueue<>(VOICE_MAX_QUEUE_SIZE);
    private SendWorker mSendWorker;
    private ReceiveWorker mReceiveWorker;
    private SocketAddress mRequestAddr;


    public NetWorker(UdpSocket udpSocket) {
        mUdpSocket = udpSocket;
        mSendWorker = new SendWorker();
        mReceiveWorker = new ReceiveWorker();
    }

    public void setSocket(UdpSocket socket) {
        mUdpSocket = socket;
    }

    public void setRequestAddr(SocketAddress socketAddress) {
        mRequestAddr = socketAddress;
    }

    public void start() {
        mSendWorker = new SendWorker();
        mReceiveWorker = new ReceiveWorker();
        mNetPool.submit(mSendWorker);
        mNetPool.submit(mReceiveWorker);
    }

    public void close() {
        setFlag(false);
        mSendQueue.clear();
        if (mUdpSocket != null) {
            mUdpSocket.close();
        }
        mUdpSocket = null;
        try {
            mSendQueue.put(new UdpPacket());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mRequestAddr = null;
        mNetPool.shutdown();
        mHandlerPool.shutdown();
    }

    private class SendWorker extends WorkerImp {

        public SendWorker() {
            super(SendWorker.class.getName());
        }

        @Override
        public void work() {
            while (waitFlag) {
                try {
                    UdpPacket sendPacket = mSendQueue.take();
                    if (sendPacket.getPacket() != null) {
                        try {
                            mUdpSocket.sendMsgToNetwork(sendPacket);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class ReceiveWorker extends WorkerImp {


        public ReceiveWorker() {
            super(ReceiveWorker.class.getName());
        }

        @Override
        public void work() {
            NetWorker.this.work();
        }
    }

    public synchronized void sendPacketData(byte[] data, byte[] action, SocketAddress socketAddress) throws IOException {
        if (isConnected()) {
            Message message = new MessageBuilder(new GroupMessage()).setAction(action).setData(data).setDataLength(data.length).create();
            UdpPacket udpPacket = new UdpPacket().setMessage(message).setSocketAddress(socketAddress).setDefaultDatagramPacket();
            try {
                mSendQueue.put(udpPacket);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*try {
                mUdpSocket.sendMsgToNetwork(udpPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        } else {
            throw new IOException("UDP Socket Status is not connected :" + mUdpSocket.getSocketInetAddress());
        }
    }

    public synchronized void sendPacketData(byte[] data, byte[] action) throws IOException {
        if (isConnected()) {
            Message message = new MessageBuilder(new GroupMessage()).setAction(action).setData(data).setDataLength(data.length).create();
            UdpPacket udpPacket = new UdpPacket().setMessage(message).setSocketAddress(mRequestAddr).setDefaultDatagramPacket();
            try {
                mSendQueue.put(udpPacket);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*try {
                mUdpSocket.sendMsgToNetwork(udpPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        } else {
            throw new IOException("UDP Socket Status is not connected :" + mUdpSocket.getSocketInetAddress());
        }
    }

    public synchronized void sendMessage(UdpPacket udpPacket) throws IOException {
        if (isConnected()) {
           try {
                udpPacket.setSocketAddress(mRequestAddr).setDefaultDatagramPacket();
                mSendQueue.put(udpPacket);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*try {
                mUdpSocket.sendMsgToNetwork(udpPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        } else {
            throw new IOException("UDP Socket Status is not connected :" + mUdpSocket.getSocketInetAddress());
        }
    }

    public UdpPacket receiveMessage(int size) throws SocketTimeoutException {
        return mUdpSocket.receiveMsgFromNetwork(size);
    }

    public boolean isConnected() {
        return mUdpSocket != null && !mUdpSocket.isClosed();
    }

    public abstract void work();

    public void setFlag(boolean flag) {
        this.waitFlag = flag;
    }
}

